#include "barkgen.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
extern "C"{
    extern void APIENTRY glBindRenderbufferEXT (GLenum, GLuint);
    extern void APIENTRY glDeleteRenderbuffersEXT (GLsizei, const GLuint *);
    extern void APIENTRY glGenRenderbuffersEXT (GLsizei, GLuint *);
    extern void APIENTRY glActiveTexture (GLenum);
    extern void APIENTRY glFramebufferTexture2DEXT (GLenum, GLenum, GLenum, GLuint, GLint);
    extern void APIENTRY glRenderbufferStorageEXT (GLenum, GLenum, GLsizei, GLsizei);
    extern void APIENTRY glFramebufferRenderbufferEXT (GLenum, GLenum, GLenum, GLuint);
}
BarkGen::BarkGen():stressConstant(STRESS_CONSTANT)
{
    lenticelColor = float3(252.0/255,244.0/255,198.0/255);
    splitColor = float3(67.0/255,63.0/255,51.0/255);
    commonColor = float3(166.0/255,145.0/255, 124.0/255);
    cur_split_id = 0;
}



//5. integrate with rest of code and matt's meshes/age stuff, more bark types, interaction with bark
//if time, grass, dof


//6. interaction 2D/3D, priority model?

//get a test age variable, see how it develops as a flat texture as it ages, then wrap the texture.
//to actually make it work, have a bud unit call initBark, every time the bark ages, call ageBark(), then barkToTexture(), then later, bind the texture stored in the bark and texture it

//intialize initial bark structure with common and lenticels, splits get added during growth, initialize thickness as well, stress
void BarkGen::initBark(BarkStruct &bark){
    perlinNoiseGen(bark.barkInfo);
    for (int i = 0; i < N_LENTICELS; i++){
        int i = rand()%BARK_HEIGHT*7.0/8.0+BARK_HEIGHT*1.0/16.0;
        int j = rand()%BARK_WIDTH;
        int index= coordToIndex(i,j);
        if (bark.barkInfo[index].b_type != SPLIT && bark.barkInfo[index].b_type != LENTICEL){
            //assert(0);//should be no lenticels
            bark.barkInfo[index].b_type = LENTICEL;
            bark.barkInfo[index].thickness = bark.barkInfo[index].thickness*(1-bark.barkInfo[index].stress);
        }
    }
    for (int i = 0; i < BARK_HEIGHT;  i++){
        for (int j = 0; j < BARK_WIDTH; j++){
            int index = coordToIndex(i,j);
            BarkUnit * curUnit = &bark.barkInfo[index];
            if (curUnit->b_type != LENTICEL){
                curUnit->b_type = COMMON;
            }
            curUnit->age = 0;
            curUnit->active = true;
            curUnit->widen_rate = calculateWidenRate(j);
        }
    }
    bark.age = 0;
    glGenTextures(1, &bark.barkID);
    glGenTextures(1, &bark.normalID);
}


int BarkGen::coordToIndex(int i, int j){
    if ( i >= 0 && i < BARK_HEIGHT && j >= 0 && j < BARK_WIDTH){
        return i*BARK_WIDTH+j;
    }
    return -1;
}
int BarkGen::coordToIndexWrap(int i, int j){
    j = (j+BARK_WIDTH) % BARK_WIDTH;
    if ( i >= 0 && i < BARK_HEIGHT){
        return i*BARK_WIDTH+j;
    }
    return -1;
}
void BarkGen::perlinNoiseGen(BarkUnit *b_info){
    for (int i = 0; i < BARK_HEIGHT;  i++){
        for (int j = 0; j < BARK_WIDTH; j++){
            int index = coordToIndex(i,j);
            b_info[index].thickness = STARTING_THICKNESS;
            b_info[index].normal = float3(0,0,1);
            b_info[index].stress = STRESS_CONSTANT/b_info[index].thickness;
            //make sure it's normalized
        }
    }
}

/*ALL SPLIT STUFF*/
void BarkGen::generateSplits(BarkStruct &bark){
    int numSplits = 0;
    int numAttempts = 0;
    while (numSplits < M_SPLITS && numAttempts < 100){
        int initRow = rand()%(BARK_HEIGHT-2)+1;
        int initCol = rand()%(BARK_WIDTH-2)+1;
        int curRow = initRow;
        int curCol = initCol;
        int index = coordToIndex(curRow,curCol);
        BarkUnit *curUnit = &bark.barkInfo[index];
        if (curUnit->stress <= SPLIT_STRESS_THRESHOLD && curUnit->b_type != LENTICEL && !isNearStop(bark.barkInfo, curRow, curCol) && rand()%100/100.0 < SPLIT_PROB){
            //Deal with tail
            curUnit->b_type = SPLIT;
            curUnit->active = true;
            curUnit->terminal = TAIL;
            curUnit->path = SOUTH;
            curUnit->thickness = curUnit->thickness*(1-curUnit->stress);
            curUnit->expand = true;
            curUnit->split_id = cur_split_id;
            //curUnit->age = 0;
            //extend the tail, then reset curUnit and curUnit's path and extend the head
            //while extending, set new tip to head or tail, set previous to NEITHER
            while (curUnit->active){
                CUR_DIRECTION direct = extendSplit(bark, curUnit, curRow, curCol);
                curUnit->terminal = TAIL;
                //curUnit->thickness = curUnit->thickness*(1-curUnit->stress);//handled farther down
                if(curRow == 0 || curRow == BARK_HEIGHT-1 || rand()%1000 < 1 || isNearStop(bark.barkInfo, curRow,curCol) ){
                    curUnit->active = false;
                }
            }

            curRow = initRow;
            curCol = initCol;
            //Deal with head
            curUnit =&bark.barkInfo[index];
            curUnit->active = true;
            curUnit->terminal = HEAD;
            curUnit->path = NORTH;
            //curUnit->age = 0;
            while (curUnit->active){
                CUR_DIRECTION direct = extendSplit(bark, curUnit, curRow, curCol);
                curUnit->terminal = HEAD;
                 //curUnit->thickness = curUnit->thickness*(1-curUnit->stress);
                if(curRow == 0 || curRow == BARK_HEIGHT-1 || rand()%1000 < 5|| isNearStop(bark.barkInfo, curRow,curCol) ){
                    curUnit->active = false;
                }
            }
            cur_split_id++;
            numSplits++;
        }
        numAttempts ++;
    }
}

CUR_DIRECTION BarkGen::extendSplit(BarkStruct &bark, BarkUnit * curUnit, int &curRow, int &curCol){
    float stresses[8];
    getSurroundingStress(bark.barkInfo, stresses, curRow, curCol);
    float probs[8];
    switch(curUnit->path){
        case NORTH:
            getProbabilities( probs, stresses, stress_template, north_template );
        break;
        case NORTHEAST:
            getProbabilities( probs, stresses, stress_template, northeast_template );
        break;
        case EAST:
            getProbabilities( probs, stresses, stress_template, east_template );
        break;
        case SOUTHEAST:
            getProbabilities( probs, stresses, stress_template, southeast_template );
        break;
        case SOUTH:
            getProbabilities( probs, stresses, stress_template, south_template );
        break;
        case SOUTHWEST:
            getProbabilities( probs, stresses, stress_template, southwest_template );
        break;
        case WEST:
            getProbabilities( probs, stresses, stress_template, west_template );
        break;
        case NORTHWEST:
            getProbabilities( probs, stresses, stress_template, northwest_template );
        break;
    }

    //probabilities gotten, now see if it advances or not based on probabilities, reset path and new tail, and curRow/curCol
    //Change this part, normalize and sample
    float prob_sum = 0;
    for (int i = 0; i < 8; i++){
            prob_sum += probs[i];
    }
    float cursum = 0;
    for (int i = 0; i < 8; i++){
        probs[i] = cursum + probs[i];
        cursum = probs[i];
    }
    float rand_float = rand()%1000*(prob_sum/1000.0);
    CUR_DIRECTION direct = (CUR_DIRECTION)0;
    float lower_bound = 0;
    for (int i = 0; i < 8; i++){
        if (rand_float > lower_bound && rand_float < probs[i]){
            direct = (CUR_DIRECTION)i;
            break;
        }
        else{
            lower_bound = probs[i];
        }
    }
    switch (direct){
        case NORTH:
            curRow++;
        break;
        case NORTHEAST:
            curRow++;
            curCol++;
        break;
        case EAST:
            curCol++;
        break;
        case SOUTHEAST:
            curCol++;
            curRow--;
        break;
        case SOUTH:
            curRow--;
        break;
        case SOUTHWEST:
            curRow--;
            curCol--;
        break;
        case WEST:
            curCol--;
        break;
        case NORTHWEST:
            curRow++;
            curCol--;
        break;
    }

    curUnit->terminal = NEITHER;
    curUnit = &bark.barkInfo[coordToIndexWrap(curRow, curCol)];
    curUnit->active = true;
    curUnit->b_type = SPLIT;
    //curUnit->age = 0;
    curUnit->path = (CUR_DIRECTION)direct;
    curUnit->thickness = curUnit->thickness*(1-curUnit->stress);
    curUnit->expand = true;
    curUnit->split_id = cur_split_id;
    return (CUR_DIRECTION)direct;
}

int BarkGen::calculateWidenRate(int i){
    int rate =  (int)(SPLIT_LOW+(abs(i-BARK_HEIGHT/2.0)/(BARK_HEIGHT/2.0))*(SPLIT_HIGH-SPLIT_LOW));
    assert(rate != 0);
   return rate;
}

void BarkGen::getSurroundingStress(BarkUnit * info, float *stress, int i, int j){
    //needs to start at top going around in clockwise order
    float2 offsets[8] = {
        float2(-1, 0),
        float2(-1, 1),
        float2(0, 1),
        float2(1, 1),
        float2(1, 0),
        float2(1, -1),
        float2(0, -1),
        float2(-1,-1)
    };
    int index = coordToIndex(i,j);
    BarkUnit *curUnit = &info[index];
    for (int off = 0; off < 8; off++){
        int cur_index = coordToIndexWrap(i+offsets[off].x, j+offsets[off].y);
        assert(cur_index != -1);
        stress[off] = info[cur_index].stress;
    }
}

void BarkGen::getProbabilities(float * probs, float * stress, const float * stresst, const float * directiont){
    float total_probs = 0;
    for (int off = 0; off < 8; off++){
        total_probs += stress[off]*stresst[off]*directiont[off];
    }

    for (int off = 0; off < 8; off++){
        probs[off] = stress[off]*stresst[off]*directiont[off]/total_probs;
    }
}

bool BarkGen::isNearStop(BarkUnit * info, int i, int j){
    float2 offsets[8] = {
        float2(-1, 0),
        float2(-1, 1),
        float2(0, 1),
        float2(1, 1),
        float2(1, 0),
        float2(1, -1),
        float2(0, -1),
        float2(-1,-1)
    };
    int index = coordToIndex(i,j);
    BarkUnit *curUnit = &info[index];
    bool stop = false;
    for (int off = 0; off < 8; off++){
        int cur_index = coordToIndexWrap(i+offsets[off].x, j+offsets[off].y);
        if (cur_index == -1 ||
            info[cur_index].b_type == LENTICEL ||
                                (info[cur_index].b_type == SPLIT && info[cur_index].split_id != curUnit->split_id
                                 && info[cur_index].age == curUnit->age)){
            stop = true;
            break;
        }
    }
    return stop;

}

/*LENTICELS*/
void BarkGen::updateLenticels(BarkUnit * b_unit, BarkUnit * bark_info, int i, int j){
    BarkUnit *curUnit = b_unit;

    if (curUnit->b_type == LENTICEL){
        int n = 0;
        float stress_total = 0;
        for (int offR = -1; offR <= 1; offR++){
            for (int offC = -1; offC <= 1; offC++){
                int adjacent_index = coordToIndexWrap(i+offR, j+offC);
                if (adjacent_index != -1){
                    n++;
                    BarkUnit *adjacentUnit = &bark_info[adjacent_index];
                    stress_total += adjacentUnit->stress;
                }
            }
        }

        int offset_index = 0;
        for (int offR = -1; offR <= 1; offR++){
            for (int offC = -1; offC <= 1; offC++){
                int adjacent_index = coordToIndexWrap(i+offR, j+offC);
                if (adjacent_index != -1){
                    BarkUnit *adjacentUnit = &bark_info[adjacent_index];
                    float assimilation_probability = LENTICEL_EXPANSION_SPEED*lenticel_template[offset_index]*(float)(adjacentUnit->stress/((1.0/n)*stress_total));
                    if(adjacentUnit->b_type != LENTICEL && adjacentUnit->active && adjacentUnit->b_type != SPLIT && (rand()%1000/1000.0) <= assimilation_probability){
                        adjacentUnit->b_type = LENTICEL;
                        adjacentUnit->thickness= adjacentUnit->thickness*(1-adjacentUnit->stress);
                        adjacentUnit->active = false;
                    }
                }
                offset_index++;
            }
        }
    }
}
//CHANGE TO ALGORITHM: constant texture size
/*THE MAIN LOOP*/
void BarkGen::ageBark(BarkStruct &bark){
    generateSplits(bark);
    for (int i = 0; i < BARK_HEIGHT;  i++){
        for (int j = 0; j < BARK_WIDTH; j++){
            int index = coordToIndex(i,j);
            BarkUnit * curUnit = &bark.barkInfo[index];
            if (curUnit->b_type == LENTICEL && curUnit->active){
                updateLenticels(curUnit, bark.barkInfo, i, j);
            }
        }
    }

    for (int i = 0; i < BARK_HEIGHT;  i++){
        for (int j = 0; j < BARK_WIDTH; j++){
            int index = coordToIndex(i,j);
            BarkUnit * curUnit = &bark.barkInfo[index];
            if (curUnit->b_type == SPLIT && curUnit->expand == true){
                if (curUnit->age%curUnit->widen_rate == curUnit->widen_rate-1){
                    int left = coordToIndexWrap(i,j-1);
                    int right = coordToIndexWrap(i,j+1);
                    BarkUnit *leftUnit = &bark.barkInfo[left];
                    BarkUnit *rightUnit = &bark.barkInfo[right];
                    if (leftUnit->b_type != SPLIT ){
                        leftUnit->widen_rate = curUnit->widen_rate;
                        leftUnit->b_type = SPLIT;
                        //leftUnit->age = 0;
                        leftUnit->age = curUnit->age;
                        leftUnit->terminal = NEITHER;
                        leftUnit->active = false;
                        leftUnit->expand = false;

                    }
                    if (rightUnit->b_type != SPLIT){
                        rightUnit->widen_rate = curUnit->widen_rate;
                        rightUnit->b_type = SPLIT;
                        //rightUnit->age = 0;
                        rightUnit->age = curUnit->age;
                        rightUnit->active = false;
                        rightUnit->terminal = NEITHER;
                        rightUnit->expand = false;
                    }
                }
            }
        }
    }
    for (int i = 0; i < BARK_HEIGHT;  i++){
        for (int j = 0; j < BARK_WIDTH; j++){

            int index = coordToIndex(i,j);
            BarkUnit * curUnit = &bark.barkInfo[index];
            if (curUnit->b_type == LENTICEL){
                curUnit->active = true;
            }
            else if (curUnit->b_type == SPLIT){
                curUnit->expand = true;
                curUnit->thickness = curUnit->thickness*(1-curUnit->stress);
            }
            curUnit->thickness += INCREMENT_THICKNESS;
            //curUnit->thickness = curUnit->thickness*(1-curUnit->stress);//erosion..?
            curUnit->stress = STRESS_CONSTANT/curUnit->thickness;
            curUnit->age++;

        }
    }
    bark.age ++;
    repopulateNormals(bark);
}
/*HANDLING NORMALS*/
void BarkGen::repopulateNormals(BarkStruct &bark){
    float2 offsets[8] = {
        float2(-1, 1),
        float2(0, 1),
        float2(1, 1),
        float2(1, 0),
        float2(1, -1),
        float2(0, -1),
        float2(-1,-1),
        float2(-1, 0)
    };
    for (int i = 0; i < BARK_HEIGHT; i++){
        for (int j = 0; j < BARK_WIDTH; j++){
            float3 avenormal(0,0,0);

            int center_index = coordToIndex(i,j);
            assert(center_index != -1);
            float3 center(j,i,bark.barkInfo[center_index].thickness);
            int total_normals = 0;
            for (int off = 0; off < 8; off++){
                int index = coordToIndexWrap(i+offsets[off].row, j+offsets[off].col);
                int index2 = coordToIndexWrap(i+offsets[(off+1)%8].row, j+offsets[(off+1)%8].col);
                if (index != -1 || index2 != -1){
                    avenormal += (float3(j+offsets[off].col,i+offsets[off].row,bark.barkInfo[index].thickness)-center).getNormalized()
                                 .cross((float3(j+offsets[(off+1)%8].col,i+offsets[(off+1)%8].row,bark.barkInfo[index2].thickness)-center).getNormalized());
                    total_normals ++;
                }
            }

            avenormal /= total_normals;
            avenormal.normalize();
            bark.barkInfo[center_index].normal = avenormal;
        }
    }

}
/*GL TEXTURING*/
void BarkGen::barkToTexture(BarkStruct &bark){//constant size texture, don't have to worry about memory allocation
    //Temporary Colors
    //TODO FINAL: instead of sampling from colors, sample from a wrapped texture instead in a shader for regular bark, splits, or lenticels
    GLfloat bark_colors[4*BARK_WIDTH * BARK_HEIGHT];
    GLfloat bark_normals[4*BARK_WIDTH*BARK_HEIGHT];
    for (int i = 0; i < BARK_HEIGHT; i++){
        for (int j = 0; j < BARK_WIDTH; j++){
            int index = coordToIndex(i,j);
            BarkUnit *curUnit = &bark.barkInfo[index];
           /* if (curUnit->b_type == LENTICEL){
                bark_colors[index*4] = lenticelColor.r;
                bark_colors[index*4+1] = lenticelColor.g;
                bark_colors[index*4+2] = lenticelColor.b;
                bark_colors[index*4+3] = 1.0;
            }
            else if (curUnit->b_type == SPLIT){
                bark_colors[index*4] = splitColor.r;
                bark_colors[index*4+1] = splitColor.g;
                bark_colors[index*4+2] = splitColor.b;
                bark_colors[index*4+3] = 1.0;
            }
            else if (curUnit->b_type == COMMON){
                bark_colors[index*4] = commonColor.r;
                bark_colors[index*4+1] = commonColor.g;
                bark_colors[index*4+2] = commonColor.b;
                bark_colors[index*4+3] = 1.0;
            }*/
            /*bark_colors[index*4] = curUnit->normal.r;
            bark_colors[index*4+1] = curUnit->normal.g;
            bark_colors[index*4+2] = curUnit->normal.b;
            bark_colors[index*4+3] = 1.0;*/
            if (curUnit->b_type == LENTICEL){
                bark_colors[index*4] = 0.0;
                bark_colors[index*4+1] = 1.0;
                bark_colors[index*4+2] = 0.0;
                bark_colors[index*4+3] = 1.0;
            }
            else if (curUnit->b_type == SPLIT){
                bark_colors[index*4] = 0.0;
                bark_colors[index*4+1] = 0.0;
                bark_colors[index*4+2] = 1.0;
                bark_colors[index*4+3] = 1.0;
            }
            else if (curUnit->b_type == COMMON){
                bark_colors[index*4] = 1.0;
                bark_colors[index*4+1] = 0.0;
                bark_colors[index*4+2] = 0.0;
                bark_colors[index*4+3] = 1.0;
            }
            bark_normals[index*4] = curUnit->normal.r/2.0+.5;
            bark_normals[index*4+1] = curUnit->normal.g/2.0+.5;
            bark_normals[index*4+2] = curUnit->normal.b/2.0+.5;
            bark_normals[index*4+3] = 1.0;
        }
    }
   // assert(count == BARK_WIDTH * BARK_HEIGHT);
    glActiveTexture(GL_TEXTURE6);
    glBindTexture(GL_TEXTURE_2D,bark.barkID);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,BARK_WIDTH,BARK_HEIGHT,0,GL_RGBA,GL_FLOAT,(void *) bark_colors);


    glActiveTexture(GL_TEXTURE5);
    glBindTexture(GL_TEXTURE_2D,bark.normalID);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,BARK_WIDTH,BARK_HEIGHT,0,GL_RGBA,GL_FLOAT,(void *) bark_normals);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D,0);
    //TODO: gl calls, Convert Bark to a texture, set the ID properly
}

void BarkGen::drawNormals(BarkStruct &bark){
    glDisable(GL_LIGHTING);
    for (int i = 0; i < BARK_HEIGHT; i++){
        for (int j = 0; j < BARK_WIDTH; j++){
            int index = coordToIndex(i,j);
            BarkUnit *curUnit = &bark.barkInfo[index];
            glLineWidth(2);
            glBegin(GL_LINES);
            glVertex3f(j, i, curUnit->thickness);
            glVertex3f(curUnit->normal.x+j, curUnit->normal.y+i, curUnit->normal.z+curUnit->thickness);
            glEnd();
        }
    }
        glEnable(GL_LIGHTING);
}
